﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;

namespace MainPaper
{
    class Paper18
    {
        private static BigInteger p; 
        private static BigInteger q;
        private static BigInteger n;
        private static BigInteger[] xi;
        private static BigInteger[] yi;
        private static BigInteger[] ri;
        private static BigInteger[] ki;
        private static Point<BigInteger>[] additionalPoints;
        private static HashSet<BigInteger> xCoordinates; 

        private static MersenneTwister generator;
        private static SHA1 sha;
        /// <summary>
        /// Number of users
        /// </summary>
        private static int t;
        private static bool _specialExecution;

        private static Dictionary<int, BigInteger> ps = new Dictionary<int, BigInteger>();
        private static Dictionary<int, BigInteger> qs = new Dictionary<int, BigInteger>();  

        public static long Execute(int numberOfParticipants, int bitlength, bool specialExecution)
        {
            _specialExecution = specialExecution;
            sha = new SHA1CryptoServiceProvider();
            generator = new MersenneTwister();
            xCoordinates = new HashSet<BigInteger>();
            t = numberOfParticipants;
            Initialization(bitlength);
            UserRegistration();

            long time = 0;
            var sw = new Stopwatch();
            sw.Start();
            time += GroupKeyGenerationAndDistribution();
            sw.Stop();
            return specialExecution? time : sw.ElapsedMilliseconds;
        }

        private static void Initialization(int bitlength)
        {
            if (ps.ContainsKey(bitlength))
            {
                p = ps[bitlength];
                q = qs[bitlength];
            }
            else
            {
                p = AlgoAndMath.NextSafePrime(BigInteger.Pow(2, bitlength));
                //p = BigInteger.Parse("281474976711563");
                //p = BigInteger.Parse("340282366920938463463374607431768223907");
                //q = AlgoAndMath.NextSafePrime(BigInteger.Pow(2, 49));
                q = AlgoAndMath.NextSafePrime(p + 1);
                //q = BigInteger.Parse("562949953422839");
                //q = BigInteger.Parse("680564733841876926926749214863536439727");
                ps.Add(bitlength,p);
                qs.Add(bitlength,q);
            }
           
            n = p * q;
        }

        private static void UserRegistration()
        {
            xi = new BigInteger[t];
            yi = new BigInteger[t];
            ri = new BigInteger[t];
            ki = new BigInteger[t];
            additionalPoints = new Point<BigInteger>[t];
            for (int i = 0; i < t; i++)
            {
                xi[i] = AlgoAndMath.RandomBigInt(generator, n);
                while (xi[i] % p == 0 || xi[i] % q == 0 || xCoordinates.Contains(xi[i])) // need to look for another number since this one is not a coprime with n
                {
                    xi[i] = AlgoAndMath.RandomBigInt(generator, n);
                }
                xCoordinates.Add(xi[i]);
                yi[i] = AlgoAndMath.RandomBigInt(generator, n);
                while (yi[i] % p == 0 || yi[i] % q == 0) // need to look for another number since this one is not a coprime with n
                {
                    yi[i] = AlgoAndMath.RandomBigInt(generator, n);
                }
            }
        }

        private static long GroupKeyGenerationAndDistribution()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            //step 1
            //step 2
            //step 3
            for (int i = 0; i < t; i++)
            {
                ri[i] = AlgoAndMath.RandomBigInt(generator, n); 
                while (ri[i] % p == 0 || ri[i] % q == 0) // need to look for another number since this one is not a coprime with n
                {
                    ri[i] = AlgoAndMath.RandomBigInt(generator, n);
                }
                if (_specialExecution) sw.Stop();
            }
            sw.Start();
            //step 4
            var key = AlgoAndMath.RandomBigInt(generator, n);
            while (key % p == 0 || key % q == 0 || xCoordinates.Contains(key)) // need to look for another number since this one is not a coprime with n
            {
                key = AlgoAndMath.RandomBigInt(generator, n);
            }
            xCoordinates.Add(key);
            var points = new Point<BigInteger>[t+1];
            points[0] = (new Point<BigInteger>(0, key));
            for (int i = 0; i < t; i++)
            {
                points[i + 1] = (new Point<BigInteger>(xi[i], yi[i] ^ ri[i]));
            }
            var polynomial = new LagrangeInterpolation(points);
            polynomial.ComputeAtPoint(0, n);
            for (int i = 0; i < t; i++)
            {
                var tmpPoint = AlgoAndMath.RandomBigInt(generator, n);
                while (tmpPoint % p == 0 || tmpPoint % q == 0 || xCoordinates.Contains(tmpPoint)) // need to look for another number since this one is not a coprime with n
                {
                    tmpPoint = AlgoAndMath.RandomBigInt(generator, n);
                }
                xCoordinates.Add(tmpPoint);
                additionalPoints[i] = (new Point<BigInteger>(tmpPoint, polynomial.ComputeAtPoint(tmpPoint,n)));
            }
            //hash computation
            var hash = ComputeBigHash(key);


            //step 5
            
            for (int i = 0; i < t; i++)// for each participant
            {
                var participantPointSet = new Point<BigInteger>[t+1];
                participantPointSet[0] = (new Point<BigInteger>(xi[i], yi[i] ^ ri[i]));
                for (int j = 0; j < t; j++) // add all additional points Pi
                {
                    participantPointSet[j + 1] = (additionalPoints[j]);
                }
                var reconstructedPolynomial = new LagrangeInterpolation(participantPointSet);
                ki[i] = reconstructedPolynomial.ComputeAtPoint(0, n);
                //hash computation
                var hashPrime = ComputeBigHash(ki[i]);
                if (_specialExecution) sw.Stop();
            }
            hash = null;
            return sw.ElapsedMilliseconds;
        }

        private static byte[] ComputeBigHash(BigInteger secret)
        {
            // big hash computation
           
            var arr = secret.ToByteArray();
            for (int i = 0; i < t; i++)
            {
                var riArr = ri[i].ToByteArray();
                Array.Resize(ref arr, arr.Length + riArr.Length);
                Array.Copy(riArr, 0, arr, arr.Length - riArr.Length, riArr.Length);
            }
            for (int i = 0; i < t; i++)
            {
                var xArr = additionalPoints[i].X.ToByteArray();
                var yArr = additionalPoints[i].Y.ToByteArray();
                Array.Resize(ref arr, arr.Length + xArr.Length + yArr.Length);
                Array.Copy(xArr, 0, arr, arr.Length - xArr.Length - yArr.Length, xArr.Length);
                Array.Copy(yArr, 0, arr, arr.Length - yArr.Length, yArr.Length);
            }
            var hash = sha.ComputeHash(arr);
            return hash;
        }
    }
}
